Skip to content

Share commentPrefix's rule in one place in LibCodeGenSlow - #107

Merged
thedavidmeister merged 1 commit into
mainfrom
2026-08-16-issue-73
Aug 17, 2026
Merged

Share commentPrefix's rule in one place in LibCodeGenSlow#107
thedavidmeister merged 1 commit into
mainfrom
2026-08-16-issue-73

Conversation

@thedavidmeister

Copy link
Copy Markdown
Contributor

Closes #73

What

bytes(comment).length == 0 ? "\n" : string.concat("\n", comment, "\n") was
pasted into all four …ConstantStringSlow builders in
test/lib/LibCodeGenSlow.sol. That is LibCodeGen.commentPrefix's body
character for character, in four places. The four copies now call one
commentPrefixSlow.

Keeping the reference independently derived

LibCodeGenSlow is the differential reference for LibCodeGen. Its value is
that it derives the expected output instead of restating the implementation, so
a shared design error shows up as a disagreement rather than moving both sides
at once. Lifting the pasted expression verbatim into a shared function would
have turned four restatements into one restatement — the same defect, better
factored — so that is not what this does.

commentPrefixSlow derives the prefix from the rule as stated in prose rather
than from the code that implements it. The rule is "a declaration is preceded by
one blank line always, and by a comment line only when there is a comment", so
the reference lists those lines and joins them, terminating each:

string[] memory lines = new string[](bytes(comment).length == 0 ? 1 : 2);
lines[0] = "";
if (lines.length == 2) {
    lines[1] = comment;
}
string memory prefix = "";
for (uint256 i = 0; i < lines.length; i++) {
    prefix = string.concat(prefix, lines[i], "\n");
}

LibCodeGen.commentPrefix chooses between two whole prefix literals. The
reference spells out neither of those literals, and states the line count and
the per-line text as separate facts. Neither side can be updated by pasting from
the other, which is the property the file exists to have.

lines[0] = "" is redundant in the sense that a fresh string[] element is
already the empty string. It is kept because the blank line is half the rule and
the array is where the rule is stated. M4 below breaks that line and the suite
reds, so it is load bearing for the tests as written, not decoration.

The library's @notice disclosed the copy as the one place the two sides state
the same rule. That is no longer what the file does, so the paragraph now
describes the line assembly.

Where this went past the issue's proposed fix

The issue asked to extract commentPrefixSlow and keep the docstring's
disclosure attached to it. Extracting alone would have left a verbatim
restatement, so the body was re-derived as above, and the disclosure was
replaced rather than moved — there is no longer a restatement to disclose. The
docstring on commentPrefixSlow says how its derivation differs from the
library's instead.

Tests

test/lib/LibCodeGen.commentPrefix.t.sol is new, next to its siblings in
test/lib/. Two literal assertions pin commentPrefixSlow's own two cases so
the reference is not held up only by agreeing with the implementation, and a
fuzz asserts LibCodeGen.commentPrefix(comment) == LibCodeGenSlow.commentPrefixSlow(comment) over 2048 comments. The four
builders' existing differential fuzzes cover the extraction not changing what
they emit.

QA

  • Discriminating tests: testCommentPrefixSlowEmptyComment,
    testCommentPrefixSlowWithComment, testCommentPrefixMatchesSlow - each
    fails on base, verified by running
    nix develop -c forge test --match-path test/lib/LibCodeGen.commentPrefix.t.sol
    on the branch with the test written and commentPrefixSlow not yet added:
    the compile fails with Error (9582): Member "commentPrefixSlow" not found
    (full output below).
  • Mutations applied: 8, all killed, full matrix with per-mutant failure counts
    and killing tests in the table below. ? 1 : 2 -> ? 2 : 2 ->
    testCommentPrefixSlowEmptyComment; lines[1] = comment -> lines[1] = ""
    -> testCommentPrefixSlowWithComment; drop the "\n" terminator in the join
    -> both literal pins; lines[0] = "" -> lines[0] = "X" -> both literal
    pins; loop i = 0 -> i = 1 -> both literal pins; and three mutants of
    LibCodeGen.commentPrefix itself (? "\n" -> ? "", drop the trailing
    "\n", == 0 -> != 0) each killed through testCommentPrefixMatchesSlow.
  • Oracle: the rule as stated in prose in LibCodeGen.commentPrefix's docstring
    and in the forge fmt behaviour it exists for - one blank line always, a
    comment line only when there is a comment. The two literal assertions carry
    the expected strings written out, and commentPrefixSlow builds from the
    list of lines rather than from the implementation's expression, so neither
    the reference nor the tests take their expectation from LibCodeGen.
  • Category check: the issue asks for one thing, the four pasted copies of
    commentPrefix's body at LibCodeGenSlow.sol:72,84,96,110 - covered, all
    four now call commentPrefixSlow, grep finds no remaining copy of the
    expression in the file. The issue's own proposed fix (extract and move the
    docstring disclosure) is covered and extended: the body is re-derived rather
    than lifted, so there is no restatement left to disclose.

Red first. With the test written and commentPrefixSlow not yet added,
nix develop -c forge test --match-path test/lib/LibCodeGen.commentPrefix.t.sol:

Compiler run failed:
Error (9582): Member "commentPrefixSlow" not found or not visible after argument-dependent lookup in type(library LibCodeGenSlow).
  --> test/lib/LibCodeGen.commentPrefix.t.sol:21:18:
   |
21 |         assertEq(LibCodeGenSlow.commentPrefixSlow(""), "\n");
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The finding is a code-quality one — four copies of a rule that already agreed
with each other — so there is no behavioural red to show. The red is that the
shared function and the assertions that hold it did not exist. The mutation
matrix below is what demonstrates the new code is covered rather than merely
present.

Green after. nix develop -c forge test:
Ran 17 test suites: 137 tests passed, 0 failed, 0 skipped (137 total tests)
— 134 before this branch, plus the three new ones.

nix develop -c forge fmt --check exits 0, no files listed.

Mutation matrix. Each mutant: applied by sed, the edit proved to have
landed by a non-empty git diff before the suite ran (a pattern that matched
nothing aborts as INVALID rather than reading as "survived"), whole suite run,
git checkout to restore. Control run first: 137 passed / 0 failed. Every
mutant is killed; the counts are out of the same 137.

# file mutation result
control none 137 passed / 0 failed
M1 LibCodeGenSlow ? 1 : 2? 2 : 2 (comment line emitted for an empty comment) 6 failed, incl. testCommentPrefixSlowEmptyComment, testCommentPrefixMatchesSlow
M2 LibCodeGenSlow lines[1] = commentlines[1] = "" 13 failed, incl. testCommentPrefixSlowWithComment, testCommentPrefixMatchesSlow
M3 LibCodeGenSlow drop the "\n" line terminator in the join 14 failed, incl. both literal pins and the fuzz
M4 LibCodeGenSlow lines[0] = ""lines[0] = "X" (blank first line) 14 failed, incl. both literal pins and the fuzz
M5 LibCodeGenSlow join loop i = 0i = 1 (skip the blank line) 14 failed, incl. both literal pins and the fuzz
M6 LibCodeGen ? "\n"? "" (no blank line when no comment) 13 failed, incl. testCommentPrefixMatchesSlow
M7 LibCodeGen drop the trailing "\n" after the comment 61 failed, incl. testCommentPrefixMatchesSlow
M8 LibCodeGen == 0!= 0 63 failed, incl. testCommentPrefixMatchesSlow

M1–M5 mutate the new reference and are killed by the new tests and by the four
builders' differentials. M6–M8 mutate the implementation the reference guards
and are killed through the differential, so the extraction did not cost the
suite any of the discrimination the four copies had.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Aug 16, 2026
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@thedavidmeister, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 56 minutes

Limit details: You’ve used all 1 included review currently available under your plan.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 831c9ffb-e519-4255-a492-b75d7664ccd6

📥 Commits

Reviewing files that changed from the base of the PR and between 935c725 and ed57563.

📒 Files selected for processing (2)
  • test/lib/LibCodeGen.commentPrefix.t.sol
  • test/lib/LibCodeGenSlow.sol

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LibCodeGenSlow restates commentPrefix's rule four times

2 participants